package webport_sp1

import (
	"encoding/binary"
	"errors"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/rs/zerolog/log"
	"wiseverds.com/frame/webport/src/wsmux"
)

var ServerAddr = "localhost:12680"

type Proxy struct {
	Client     *wsmux.Session
	clientLock sync.Mutex
	Server     *wsmux.Session
	serverLock sync.Mutex
	running    bool
}

type LocalConn struct {
	ID         uint64
	Conn       net.Conn
	ServerName string
	ClientAddr string
}

func NewProxy() *Proxy {
	return &Proxy{
		running: false,
		// connCounter: 0,
	}
}

func (p *Proxy) Start() {
	p.running = true
	go p.keepWSConn()
}

func (p *Proxy) Stop() {
	p.running = false

	// Close the WebSocket mux connection
	if p.Client != nil {
		p.Client.Close()
		p.Client = nil
	}
}

func (p *Proxy) keepWSConn() {
	for {
		if !p.running {
			break
		}

		done := make(chan bool, 1)
		p.newWSConn(done) // this function does not block

		select {
		case <-done:
			time.Sleep(5 * time.Second)
		}
	}
}

// Create a new WebSocket connection to the remote server,
// and create a new WebSocket mux Client.
func (p *Proxy) newWSConn(done chan bool) {
	wsUrl := url.URL{
		Scheme: "ws",
		Host:   ServerAddr,
		Path:   "/partner-service/service1/",
	}

	wsUrlString := wsUrl.String()

	log.Printf("webport_sp1: connecting to %s", wsUrlString)
	requestHeader := http.Header{
		"X-Webport-Service": []string{"sp1"},
	}

	wsConn, response, err := websocket.DefaultDialer.Dial(wsUrlString, requestHeader)
	if err != nil {
		log.Printf("webport_sp1: connection error: %v", err)
		done <- true
		return
	}

	log.Printf("response header: %v\n", response.Header)

	log.Printf("webport_sp1: connected to %s", wsUrlString)

	// create the client session
	clientConf := wsmux.Config{
		StreamBufferSize: 4 * 1024,
		CloseCallback: func() {
			log.Info().Str("service", "webport_sp1").Str("func", "newWSConn").Msg("the WebSocket connection is closed")
			done <- true
		},
	}

	p.clientLock.Lock()
	defer p.clientLock.Unlock()
	p.Client = wsmux.Client(wsConn, clientConf)

	// accept new streams from this channel
	strChan := make(chan net.Conn, 1)
	go p.serve(strChan)
}

func (p *Proxy) serve(strChan chan net.Conn) {
	for {
		// This simulates http.Server's Serve method
		log.Printf("webport_sp1: try to accept a new connection ...")
		stream, err := p.Client.Accept()
		if err != nil {
			log.Printf("webport_sp1: server.Accept error: %v", err)
			// _, ok := err.(net.Error)
			// if !ok {
			// 	log.Printf("server.Accept error: %v", err)
			// }
			return
		}

		log.Printf("webport_sp1: accepted a new connection")
		f := &forwarder{
			stream: stream,
		}

		go f.forward()
	}
}

//------------------------------------------------------------
// handle incoming connections from ss1
//------------------------------------------------------------

func (p *Proxy) Dial(addr string) (net.Conn, error) {
	log.Debug().Str("service", "webport_sp1").Str("func", "Dial").Str("addr", addr).Msg("")

	var port int
	colonIndex := strings.Index(addr, ":")
	if colonIndex == -1 {
		port = 12720
	} else {
		hostString := addr[:colonIndex]
		if hostString != "127.0.0.1" {
			return nil, errors.New("invalid host")
		}

		portString := addr[colonIndex+1:]
		var err error
		port, err = strconv.Atoi(portString)
		if err != nil {
			return nil, errors.New("invalid port number")
		}
	}

	p.clientLock.Lock()
	defer p.clientLock.Unlock()

	if p.Client == nil {
		log.Printf("webport_sp1: handleConn: client is nil")
		return nil, errors.New("client is nil")
	}

	stream, err := p.Client.Open()
	if err != nil {
		log.Printf("webport_sp1: handleConn: failed to open stream: %v", err)
		return nil, errors.New("failed to open stream")
	}

	err = binary.Write(stream, binary.BigEndian, uint32(port))
	if err != nil {
		return nil, err
	}

	return stream, nil
}
